<template>
  <div class="topo-chart"></div>
</template>
<script lang="js">
import * as d3 from 'd3';
import calls from '../mock/calls';
import nodes from '../mock/nodes';

const diagonal = d3.linkHorizontal()
  .x((d) => d.x )
  .y((d) => d.y );
const diagonalvertical = d3.linkVertical()
  .x((d) => d.x )
  .y((d) => d.y );

export default {
  // 子组件传参
  props: {
    datas: {
      type: Object,
      default() {
        return {
          nodes: [],
          calls: [],
        };
      },
    },
  },
  data() {
    return {
      LOCAL: require('../assets/Local2.png'),
      CUBE: require('../assets/cube22.png'),
      CUBEERROR: require('../assets/cube21.png'),
      USER: require('../assets/USER.png'),
      UNKNOWN: require('../assets/UNKNOWN.png'),
      UNKNOWNCLOUD: require('../assets/UNKNOWN_CLOUD.png'),
      UNDEFINED: require('../assets/UNDEFINED.png'),
      KAFKACONSUMER: require('../assets/KAFKALOGO.png'),
      SPRINGMVC: require('../assets/SPRINGMVC.png'),
      H2: require('../assets/H2.png'),
      width: 600,
      height: 600,
      force: '',
      svg: '',
      graph: '',
      link: '',
      node: '',
      zoom: '',
    };
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.resize);
  },
  mounted() {
    window.addEventListener('resize', this.resize);
    this.height = this.$el.clientHeight || this.height;
    this.svg = d3
      .select(this.$el)
      .append('svg')
      .style('display', 'block')
      .attr('width', '100%')
      .attr('height', this.height);

    this.draw();
  },
  watch: {
    'datas.nodes': 'draw',
  },
  methods: {
    draw() {
      // 测试用
      this.datas.nodes = nodes;
      this.datas.calls = calls;

      // const codeId = this.datas.nodes.map((i) => i.id);
      // console.log(codeId);
      // for (const call of this.datas.calls) {
      //   console.log(this.datas.calls);
      //   if (codeId.indexOf(call.target) === -1 ) {
      //     call.target = call.source;
      //   }
      // }
      this.svg.select('.graph').remove();
      this.force = d3
        .forceSimulation(this.datas.nodes)
        .force('collide', d3.forceCollide().radius(() => 65)) // 创建一个新的圆的碰撞检测，半径65
        .force('yPos', d3.forceY().strength(1)) // y轴布局力度
        .force('xPos', d3.forceX().strength(1)) // x轴布局力度
        .force('charge', d3.forceManyBody().strength(-520))
        .force('link', d3.forceLink(this.datas.calls).id((d) => d.id))
        .force('center', d3.forceCenter(window.innerWidth / 2 + 100, this.height / 2))
        .on('tick', this.tick)
        .stop();

      this.graph = this.svg.append('g').attr('class', 'graph');
      this.svg.call(this.getZoomBehavior(this.graph));
      this.svg.on('click', (d, i) => {
        event.stopPropagation();
        event.preventDefault();
        this.toggleNode(this.node, d, false);
        this.toggleLine(this.line, d, false);
        this.toggleLine(this.lineNode, d, false);
      });
      this.defs = this.graph.append('defs');
      // 箭头
      this.arrowMarker = this.defs
        .append('marker')
        .attr('id', 'arrow')
        .attr('markerUnits', 'strokeWidth')
        .attr('markerWidth', '12')
        .attr('markerHeight', '12')
        .attr('viewBox', '0 0 12 12')
        .attr('refX', '11')
        .attr('refY', '6')
        .attr('orient', 'auto');
      const arrowPath = 'M2,2 L10,6 L2,10 L3,6 L2,2';
      this.arrowMarker.append('path').attr('d', arrowPath).attr('fill', '#217EF2');
      this.gnode = this.graph.append('g').selectAll('.node');
      const that = this;
      // 节点
      this.node = this.gnode.data(this.datas.nodes)
        .enter()
        .append('g')
        .call(d3.drag()
          .on('start', this.dragstart)
          .on('drag', this.dragged)
          .on('end', this.dragended),
          )
        .on('mouseover', (d, i) => {
          // todo
        })
        .on('mouseout', (d, i) => {
          // todo
        })
        .on('click', function(d, i) {
          event.stopPropagation();
          that.node.attr('class', '');
          d3.select(this).attr('class', 'node-active');
          const copyD = JSON.parse(JSON.stringify(d));
          delete copyD.x;
          delete copyD.y;
          delete copyD.vx;
          delete copyD.vy;
          delete copyD.fx;
          delete copyD.fy;
          delete copyD.index;
          that.toggleNode(that.node, d, true);
          that.toggleLine(that.line, d, true);
          that.toggleLine(that.lineNode, d, true);
        });
      this.node
        .append('image')
        .attr('width', 49)
        .attr('height', 49)
        .attr('x', 2)
        .attr('y', 10)
        .attr('style', 'cursor: move;')
        .attr('xlink:href', (d) => {
          // const type = d.type;
          if ( d.sla < 98 ) {
            return this.CUBEERROR;
          }
          return this.CUBE;
        });
      this.node
        .append('image')
        .attr('width', 32)
        .attr('height', 32)
        .attr('x', 6)
        .attr('y', -10)
        .attr('style', 'opacity: 0.5;')
        .attr('xlink:href', this.LOCAL);

      this.node
        .append('image')
        .attr('width', 18)
        .attr('height', 18)
        .attr('x', 13)
        .attr('y', -7)
        .attr('xlink:href', (d) => {
          if ( !d.type || d.type === 'N/A') {
            return this.UNDEFINED;
          }
          return this[d.type.toUpperCase().replace('-', '')];
        });

      this.node
        .append('text')
        .attr('class', 'node-text')
        .attr('text-anchor', 'middle')
        .attr('x', 22)
        .attr('y', 70)
        .text((d) => d.name.length > 12 ? `${d.name.substring(0, 12)}...` : d.name);

      this.glink = this.graph.append('g').selectAll('.link');
      this.link = this.glink.data(this.datas.calls).enter();
      this.line = this.link.append('path')
        .attr('class', 'link')
        .attr('stroke-dasharray', '13 7')
        .attr('stroke', (d) => d.cpm ? '#217EF25f' : '#6a6d7777');
      // const handleSelectLine = (d, i) => {
      //   // todo
      // };
      this.lineNode = this.link.append('rect').attr('class', 'link-node cp')
        .attr('width', 6)
        .attr('height', 6)
        .attr('rx', 3)
        .attr('ry', 3)
        .attr('fill', (d) => d.cpm ? '#217EF299' : '#6a6d7799')
        .on('click', (d, i) => {
          event.stopPropagation();
        });

      d3.timeout(() => {
        for (
          let i = 0,
            n = Math.ceil(Math.log(this.force.alphaMin()) / Math.log(1 - this.force.alphaDecay()));
          i < n;
          i += 1
        ) {
          this.force.tick();
          this.tick();
        }
      });
    },

    isLinkNode(currNode, node) {
      if (currNode.id === node.id) {
        return true;
      }
      return this.datas.calls.filter((i) =>
        (i.source.id === currNode.id || i.target.id === currNode.id) &&
        (i.source.id === node.id || i.target.id === node.id)).length;
    },
    toggleNode(nodeCircle, currNode, isHover) {
      if (isHover) {
        nodeCircle.sort((a, b) => a.id === currNode.id ? 1 : -1);
        nodeCircle
          .style('opacity', 0.2)
          .filter((node) => this.isLinkNode(currNode, node))
          .style('opacity', 1);
      } else {
        nodeCircle.style('opacity', 1);
      }
    },
    isLinkLine(node, link) {
      return link.source.id === node.id || link.target.id === node.id;
    },
    toggleLine(linkLine, currNode, isHover) {
      if (isHover) {
        linkLine
          .style('opacity', .05)
          .style('animation', 'none')
          .filter((link) => this.isLinkLine(currNode, link))
          .style('opacity', 1)
          .style('animation', 'dash 1s linear infinite');
          // .classed('link-active', true);
      } else {
        linkLine
          .style('opacity', 1)
          .style('animation', 'dash 1s linear infinite');
          // .classed('link-active', false);
      }
    },
    resize() {
      this.svg.attr('height', document.body.clientHeight - 50);
    },
    tick() {
      this.line
        .attr('d', (d) =>
          `M${d.source.x} ${d.source.y} Q ${(d.source.x + d.target.x) / 2} ${(d.target.y + d.source.y) / 2 - 80} ${d.target.x} ${d.target.y}`);
      this.lineNode.attr('transform', (d) =>
        `translate(${(d.source.x + d.target.x) / 2 - 3},${(d.target.y + d.source.y) / 2 - 43})`);
      this.node.attr('transform', (d) => `translate(${d.x -  22},${d.y - 22})`);
    },
    getZoomBehavior(g) {
      // const that = this;
      return d3
        .zoom() // 创建一个缩放行为
        .scaleExtent([0.3, 10]) // 设置缩放范围
        .on('zoom', () => {
          g.attr(
            'transform',
            `translate(${d3.event.transform.x},${d3.event.transform.y})scale(${d3.event.transform.k})`,
          );
        });
    },
    dragstart(d) {
      d3.event.sourceEvent.stopPropagation();
      this.node._groups[0].forEach((nd) => {
        nd.__data__.fx = nd.__data__.x;
        nd.__data__.fy = nd.__data__.y;
      });
      if (!d3.event.active) {
        this.force.alphaTarget(0.3).restart();
      }
    },
    dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    },
    dragended(d) {
      if (!d3.event.active) {
        this.force.alphaTarget(0);
      }
    },
  },
};
</script>

<style lang="scss">
.topo-chart {
  height: 100%;
  width: 100%;
  background: #333840;
  .node-name {
    cursor: move;
    font-size: 14px;
    fill: #ddd;
  }
  .link {
    stroke-linecap: round;
    stroke-width: 1.3px;
    fill: none;
    animation: dash 1s linear infinite;
  }
  @keyframes dash {
    from {
      stroke-dashoffset: 20;
    }
    to {
      stroke-dashoffset: 0;
    }
  }
  .node-text {
    font-family: "Lato", "Source Han Sans CN", "Microsoft YaHei", sans-serif;
    fill: #ddd;
    font-size: 11px;
    opacity: 0.8;
  }
  .link-text {
    font-family: "Lato", "Source Han Sans CN", "Microsoft YaHei", sans-serif;
    fill: #ddd;
    font-size: 11px;
    opacity: 0.8;
  }
  .node {
    cursor: move;
    fill: #333840;
    stroke-width: 0;
  }
  .link-node {
    stroke-width: 6px;
    stroke: rgba(255, 255, 255, 0);
  }
}
</style>